#include "record_desktop_duplication.h"

#include "../platform/system_lib.h"
#include "../platform/d3d_helper.h"
#include "../platform/d3d_pixelshader.h"
#include "../platform/d3d_vertexshader.h"
#include "../util/common.h"

#include "../platform/system_error.h"
#include "error_define.h"

#include <algorithm>
#include <iostream>



record_desktop_duplication::record_desktop_duplication()
{
	//_data_type = RECORD_DESKTOP_DATA_TYPES::AT_DESKTOP_BGRA;
	_imgBuf = nullptr;

	_d3d11 = nullptr;
	_dxgi = nullptr;

	_d3d_device = nullptr;
	_d3d_ctx = nullptr;
	_d3d_vshader = nullptr;
	_d3d_pshader = nullptr;
	_d3d_inlayout = nullptr;
	_d3d_samplerlinear = nullptr;

	_duplication = nullptr;
	_image = nullptr;
	_output_des = { 0 };
	_output_index = 0;

	ZeroMemory(&_cursor_info, sizeof(_cursor_info));
}


record_desktop_duplication::~record_desktop_duplication()
{
	stop();
	clean_up();
}

int record_desktop_duplication::init(const Rect& rect, const int fps)
{
	int error = AE_NO;
	if (_inited == true) {
		return error;
	}

	_fps = fps;
	_rect = rect;

	do {
		_d3d11 = load_system_library("d3d11.dll");
		_dxgi = load_system_library("dxgi.dll");

		if (!_d3d11 || !_dxgi) {
			error = AE_D3D_LOAD_FAILED;
			break;
		}

		error = init_d3d11();
		if (error != AE_NO)
			break;

		_imgBuf = (FRAME_PKG*)malloc(sizeof(FRAME_PKG));
		_imgBuf->w = rect.width();
		_imgBuf->h = rect.height();
		_imgBuf->size = (rect.width() * 32 + 31) / 32 * rect.height() * 4;
		_imgBuf->img = new uint8_t[_imgBuf->size];

		error = init_duplication();
		if (error != AE_NO) {
			break;
		}

		_inited = true;
	} while (0);

	if (error != AE_NO) {
		//al_debug("%s,last error:%s", err2str(error), system_error::error2str(GetLastError()).c_str());
	}

	return error;
}

int record_desktop_duplication::start()
{
	if (_running == true) {
		//al_warn("record desktop duplication is already running");
		return AE_NO;
	}

	if (_inited == false) {
		return AE_NEED_INIT;
	}

	_running = true;
	_thread = std::thread(std::bind(&record_desktop_duplication::record_func, this));

	return AE_NO;
}

int record_desktop_duplication::pause()
{
	_paused = true;
	return AE_NO;
}

int record_desktop_duplication::resume()
{
	_paused = false;
	return AE_NO;
}

int record_desktop_duplication::stop()
{
	_running = false;
	if (_thread.joinable())
		_thread.join();

	return AE_NO;
}

void record_desktop_duplication::clean_up()
{
	_inited = false;

	if (_imgBuf) {
		delete[] _imgBuf->img;
		free(_imgBuf);
	}
	_imgBuf = nullptr;

	if (_cursor_info.buff) {
		delete[] _cursor_info.buff;
		_cursor_info.buff = nullptr;
	}

	ZeroMemory(&_cursor_info, sizeof(_cursor_info));

	//Clean up duplication interfaces
	clean_duplication();

	//Clean up d3d11 interfaces
	clean_d3d11();

	//finally free d3d11 & dxgi library
	if (_d3d11) free_system_library(_d3d11);
	_d3d11 = nullptr;

	if (_dxgi) free_system_library(_dxgi);
	_dxgi = nullptr;
}

int record_desktop_duplication::get_dst_adapter(IDXGIAdapter** adapter)
{
	int error = AE_NO;
	do {
		auto adapters = d3d_helper::get_adapters(&error, true);
		if (error != AE_NO || adapters.size() == 0)
			break;

		for (std::list<IDXGIAdapter*>::iterator itr = adapters.begin(); itr != adapters.end(); itr++) {
			IDXGIOutput* adapter_output = nullptr;
			DXGI_ADAPTER_DESC adapter_desc = { 0 };
			DXGI_OUTPUT_DESC adapter_output_desc = { 0 };
			(*itr)->GetDesc(&adapter_desc);
			//al_debug("adaptor:%s", utils_string::unicode_ascii(adapter_desc.Description).c_str());

			unsigned int n = 0;
			RECT output_rect;
			while ((*itr)->EnumOutputs(n, &adapter_output) != DXGI_ERROR_NOT_FOUND)
			{
				HRESULT hr = adapter_output->GetDesc(&adapter_output_desc);
				if (FAILED(hr)) continue;

				output_rect = adapter_output_desc.DesktopCoordinates;

				//al_debug("  output:%s left:%d top:%d right:%d bottom:%d",
				//	utils_string::unicode_ascii(adapter_output_desc.DeviceName).c_str(),
				//	output_rect.left, output_rect.top, output_rect.right, output_rect.bottom);

				if (output_rect.left <= _rect.left() &&
					output_rect.top <= _rect.top() &&
					output_rect.right >= _rect.right() &&
					output_rect.bottom >= _rect.bottom()) {
					error = AE_NO;
					break;
				}

				++n;
			}

			if (error != AE_DXGI_FOUND_ADAPTER_FAILED) {
				*adapter = *itr;
				break;
			}
		}

	} while (0);

	return error;
}

int record_desktop_duplication::create_d3d_device(IDXGIAdapter* adapter, ID3D11Device** device)
{
	int error = AE_NO;
	do {
		PFN_D3D11_CREATE_DEVICE create_device =
			(PFN_D3D11_CREATE_DEVICE)GetProcAddress(_d3d11, "D3D11CreateDevice");
		if (!create_device) {
			error = AE_D3D_GET_PROC_FAILED;
			break;
		}

		HRESULT hr = S_OK;

		// Driver types supported
		// If you set the pAdapter parameter to a non - NULL value, 
		// you must also set the DriverType parameter to the D3D_DRIVER_TYPE_UNKNOWN value.
		D3D_DRIVER_TYPE driver_types[] =
		{
			D3D_DRIVER_TYPE_UNKNOWN,
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_WARP,
			D3D_DRIVER_TYPE_REFERENCE,
		};
		UINT n_driver_types = ARRAYSIZE(driver_types);

		// Feature levels supported
		D3D_FEATURE_LEVEL feature_levels[] =
		{
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
			D3D_FEATURE_LEVEL_9_1
		};
		UINT n_feature_levels = ARRAYSIZE(feature_levels);

		D3D_FEATURE_LEVEL feature_level;

		// Create device
		for (UINT driver_index = 0; driver_index < n_driver_types; ++driver_index)
		{
			hr = create_device(adapter, driver_types[driver_index], nullptr, 0, feature_levels, n_feature_levels,
				D3D11_SDK_VERSION, device, &feature_level, &_d3d_ctx);
			if (SUCCEEDED(hr)) break;
		}

		if (FAILED(hr))
		{
			error = AE_D3D_CREATE_DEVICE_FAILED;
			break;
		}

	} while (0);

	return error;
}

int record_desktop_duplication::init_d3d11()
{
	int error = AE_NO;

	do {
		IDXGIAdapter* adapter = nullptr;
		error = get_dst_adapter(&adapter);
		if (error != AE_NO)
			break;

		error = create_d3d_device(adapter, &_d3d_device);
		if (error != AE_NO)
			break;
		//No need for grab full screen,but in move & dirty rects copy
#if 0
			// VERTEX shader
		UINT Size = ARRAYSIZE(g_VS);
		HRESULT hr = _d3d_device->CreateVertexShader(g_VS, Size, nullptr, &_d3d_vshader);
		if (FAILED(hr))
		{
			error = AE_D3D_CREATE_VERTEX_SHADER_FAILED;
			break;
		}

		// Input layout
		D3D11_INPUT_ELEMENT_DESC layouts[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
		};
		UINT n_layouts = ARRAYSIZE(layouts);
		hr = _d3d_device->CreateInputLayout(layouts, n_layouts, g_VS, Size, &_d3d_inlayout);
		if (FAILED(hr))
		{
			error = AE_D3D_CREATE_INLAYOUT_FAILED;
			break;
		}
		_d3d_ctx->IASetInputLayout(_d3d_inlayout);

		// Pixel shader
		Size = ARRAYSIZE(g_PS);
		hr = _d3d_device->CreatePixelShader(g_PS, Size, nullptr, &_d3d_pshader);
		if (FAILED(hr))
		{
			error = AE_D3D_CREATE_PIXEL_SHADER_FAILED;
			break;
		}

		// Set up sampler
		D3D11_SAMPLER_DESC sampler_desc;
		RtlZeroMemory(&sampler_desc, sizeof(sampler_desc));
		sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
		sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
		sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
		sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
		sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
		sampler_desc.MinLOD = 0;
		sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
		hr = _d3d_device->CreateSamplerState(&sampler_desc, &_d3d_samplerlinear);
		if (FAILED(hr))
		{
			error = AE_D3D_CREATE_SAMPLERSTATE_FAILED;
			break;
		}
#endif
	} while (0);

	return error;
}

void record_desktop_duplication::clean_d3d11()
{
	if (_d3d_device) _d3d_device->Release();
	_d3d_device = nullptr;

	if (_d3d_ctx) _d3d_ctx->Release();
	_d3d_ctx = nullptr;

	if (_d3d_vshader) _d3d_vshader->Release();
	_d3d_vshader = nullptr;

	if (_d3d_pshader) _d3d_pshader->Release();
	_d3d_pshader = nullptr;

	if (_d3d_inlayout) _d3d_inlayout->Release();
	_d3d_inlayout = nullptr;

	if (_d3d_samplerlinear) _d3d_samplerlinear->Release();
	_d3d_samplerlinear = nullptr;
}

int record_desktop_duplication::init_duplication()
{
	int error = AE_NO;
	do {
		// Get DXGI device
		IDXGIDevice* dxgi_device = nullptr;
		HRESULT hr = _d3d_device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgi_device));
		if (FAILED(hr))
		{
			error = AE_D3D_QUERYINTERFACE_FAILED;
			break;
		}

		// Get DXGI adapter
		IDXGIAdapter* dxgi_adapter = nullptr;
		hr = dxgi_device->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&dxgi_adapter));
		dxgi_device->Release();
		dxgi_device = nullptr;
		if (FAILED(hr))
		{
			error = AE_DUP_GET_PARENT_FAILED;
			break;
		}



		// Get output
		IDXGIOutput* dxgi_output = nullptr;
		hr = dxgi_adapter->EnumOutputs(_output_index, &dxgi_output);
		dxgi_adapter->Release();
		dxgi_adapter = nullptr;
		if (FAILED(hr))
		{
			error = AE_DUP_ENUM_OUTPUT_FAILED;
			break;
		}

		dxgi_output->GetDesc(&_output_des);

		std::wcout << _output_des.DeviceName << std::endl;

		std::cout << "Monitor X: " << _output_des.DesktopCoordinates.right - _output_des.DesktopCoordinates.left << std::endl;
		std::cout << "Monitor Y: " << _output_des.DesktopCoordinates.bottom - _output_des.DesktopCoordinates.top << std::endl;

		DEVMODE displayMode{};
		displayMode.dmSize = sizeof(DEVMODE);

		EnumDisplaySettings(_output_des.DeviceName, ENUM_CURRENT_SETTINGS, &displayMode);

		float proc = (float)displayMode.dmPelsHeight / (_output_des.DesktopCoordinates.bottom - _output_des.DesktopCoordinates.top);
		std::cout << "DPI percentage: " << proc << "\n";
		std::cout << "Screen Start position: " << displayMode.dmPosition.x << " x " << displayMode.dmPosition.y << std::endl;


		// QI for Output 1
		IDXGIOutput1* dxgi_output1 = nullptr;
		hr = dxgi_output->QueryInterface(__uuidof(dxgi_output1), reinterpret_cast<void**>(&dxgi_output1));
		dxgi_output->Release();
		dxgi_output = nullptr;
		if (FAILED(hr))
		{
			error = AE_DUP_QI_FAILED;
			break;
		}

		// Create desktop duplication
		hr = dxgi_output1->DuplicateOutput(_d3d_device, &_duplication);
		dxgi_output1->Release();
		dxgi_output1 = nullptr;
		if (FAILED(hr))
		{
			error = AE_DUP_DUPLICATE_FAILED;
			if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
			{
				error = AE_DUP_DUPLICATE_MAX_FAILED;
			}

			//al_error("duplicate output failed,%lld", hr);
			break;
		}
	} while (0);

	return error;
}

int record_desktop_duplication::free_duplicated_frame()
{
	HRESULT hr = _duplication->ReleaseFrame();
	if (FAILED(hr))
	{
		return AE_DUP_RELEASE_FRAME_FAILED;
	}

	if (_image)
	{
		_image->Release();
		_image = nullptr;
	}

	return AE_DUP_RELEASE_FRAME_FAILED;
}

void record_desktop_duplication::clean_duplication()
{
	if (_duplication) _duplication->Release();
	if (_image) _image->Release();

	_duplication = nullptr;
	_image = nullptr;
}

bool record_desktop_duplication::attatch_desktop()
{
	HDESK desktop = nullptr;
	desktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
	if (!desktop)
	{
		// We do not have access to the desktop so request a retry
		return false;
	}

	// Attach desktop to this thread
	bool battached = SetThreadDesktop(desktop) != 0;
	CloseDesktop(desktop);

	if (!battached)
	{
		// We do not have access to the desktop so request a retry
		return false;
	}

	return true;
}

int record_desktop_duplication::get_desktop_image(DXGI_OUTDUPL_FRAME_INFO* frame_info)
{
	IDXGIResource* dxgi_res = nullptr;

	// Get new frame
	HRESULT hr = _duplication->AcquireNextFrame(500, frame_info, &dxgi_res);

	// Timeout will return when desktop has no chane
	if (hr == DXGI_ERROR_WAIT_TIMEOUT) return AE_TIMEOUT;

	if (FAILED(hr))
		return AE_DUP_ACQUIRE_FRAME_FAILED;

	// QI for IDXGIResource
	hr = dxgi_res->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&_image));
	dxgi_res->Release();
	dxgi_res = nullptr;
	if (FAILED(hr)) return AE_DUP_QI_FRAME_FAILED;

	// Copy old description
	D3D11_TEXTURE2D_DESC frame_desc;
	_image->GetDesc(&frame_desc);

	// Create a new staging buffer for fill frame image
	ID3D11Texture2D* new_image = NULL;
	frame_desc.Usage = D3D11_USAGE_STAGING;
	frame_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
	frame_desc.BindFlags = 0;
	frame_desc.MiscFlags = 0;
	frame_desc.MipLevels = 1;
	frame_desc.ArraySize = 1;
	frame_desc.SampleDesc.Count = 1;
	frame_desc.SampleDesc.Quality = 0;
	hr = _d3d_device->CreateTexture2D(&frame_desc, NULL, &new_image);
	if (FAILED(hr)) return AE_DUP_CREATE_TEXTURE_FAILED;


	// Copy next staging buffer to new staging buffer
	_d3d_ctx->CopyResource(new_image, _image);

#if 0 
	// Should calc the row pitch ,and compare dst row pitch with frame row pitch
	// Create staging buffer for map bits
	IDXGISurface* dxgi_surface = NULL;
	hr = new_image->QueryInterface(__uuidof(IDXGISurface), (void**)(&dxgi_surface));
	new_image->Release();
	if (FAILED(hr)) return AE_DUP_QI_DXGI_FAILED;

	// Map buff to mapped rect structure
	DXGI_MAPPED_RECT mapped_rect;
	hr = dxgi_surface->Map(&mapped_rect, DXGI_MAP_READ);
	if (FAILED(hr)) return AE_DUP_MAP_FAILED;

	int dst_offset_x = _rect.left() - _output_des.DesktopCoordinates.left;
	int dst_offset_y = _rect.top() - _output_des.DesktopCoordinates.top;
	int dst_rowpitch = std::min((int)frame_desc.Width, _rect.right() - _rect.left()) * 4;
	int dst_colpitch = std::min((int)_height, (int)(_output_des.DesktopCoordinates.bottom - _output_des.DesktopCoordinates.top - dst_offset_y));

	for (int h = 0; h < dst_colpitch; h++) {
		memcpy_s(_buffer + h * dst_rowpitch, dst_rowpitch,
			(BYTE*)mapped_rect.pBits + (h + dst_offset_y) * mapped_rect.Pitch + dst_offset_x * 4, std::min(mapped_rect.Pitch, dst_rowpitch));
	}


	dxgi_surface->Unmap();

	dxgi_surface->Release();
	dxgi_surface = nullptr;

#else

	D3D11_MAPPED_SUBRESOURCE resource;
	UINT subresource = D3D11CalcSubresource(0, 0, 0);

	hr = _d3d_ctx->Map(new_image, subresource, D3D11_MAP_READ_WRITE, 0, &resource);
	new_image->Release();
	if (FAILED(hr)) return AE_DUP_MAP_FAILED;

	int dst_rowpitch = frame_desc.Width * 4;
	for (int h = 0; h < frame_desc.Height; h++) {
		memcpy_s((uint8_t*)_imgBuf->img + h * dst_rowpitch, dst_rowpitch, (BYTE*)resource.pData + h * resource.RowPitch, std::min(resource.RowPitch, (UINT32)dst_rowpitch));
	}

#endif

	return AE_NO;
}

int record_desktop_duplication::get_desktop_cursor(const DXGI_OUTDUPL_FRAME_INFO* frame_info)
{
	// A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change
	if (frame_info->LastMouseUpdateTime.QuadPart == 0)
		return AE_NO;

	bool b_updated = true;

	// Make sure we don't update pointer position wrongly
	// If pointer is invisible, make sure we did not get an update from another output that the last time that said pointer
	// was visible, if so, don't set it to invisible or update.
	if (!frame_info->PointerPosition.Visible && (_cursor_info.output_index != _output_index))
		b_updated = false;

	// If two outputs both say they have a visible, only update if new update has newer timestamp
	if (frame_info->PointerPosition.Visible && _cursor_info.visible && (_cursor_info.output_index != _output_index) && (_cursor_info.pre_timestamp.QuadPart > frame_info->LastMouseUpdateTime.QuadPart))
		b_updated = false;

	// Update position
	if (b_updated)
	{
		_cursor_info.position.x = frame_info->PointerPosition.Position.x + _output_des.DesktopCoordinates.left;
		_cursor_info.position.y = frame_info->PointerPosition.Position.y + _output_des.DesktopCoordinates.top;
		_cursor_info.output_index = _output_index;
		_cursor_info.pre_timestamp = frame_info->LastMouseUpdateTime;
		_cursor_info.visible = frame_info->PointerPosition.Visible != 0;
	}

	// No new shape only update cursor positions & visible state
	if (frame_info->PointerShapeBufferSize == 0)
	{
		return AE_NO;
	}

	// Old buffer too small
	if (frame_info->PointerShapeBufferSize > _cursor_info.size)
	{
		if (_cursor_info.buff)
		{
			delete[] _cursor_info.buff;
			_cursor_info.buff = nullptr;
		}
		_cursor_info.buff = new (std::nothrow) BYTE[frame_info->PointerShapeBufferSize];
		if (!_cursor_info.buff)
		{
			_cursor_info.size = 0;
			return AE_ALLOCATE_FAILED;
		}

		// Update buffer size
		_cursor_info.size = frame_info->PointerShapeBufferSize;
	}

	// Get shape
	UINT BufferSizeRequired;
	HRESULT hr = _duplication->GetFramePointerShape(frame_info->PointerShapeBufferSize, reinterpret_cast<VOID*>(_cursor_info.buff), &BufferSizeRequired, &(_cursor_info.shape));
	if (FAILED(hr))
	{
		delete[] _cursor_info.buff;
		_cursor_info.buff = nullptr;
		_cursor_info.size = 0;
		return AE_DUP_GET_CURSORSHAPE_FAILED;
	}

	return AE_NO;
}

static unsigned int bit_reverse(unsigned int n)
{

	n = ((n >> 1) & 0x55555555) | ((n << 1) & 0xaaaaaaaa);

	n = ((n >> 2) & 0x33333333) | ((n << 2) & 0xcccccccc);

	n = ((n >> 4) & 0x0f0f0f0f) | ((n << 4) & 0xf0f0f0f0);

	n = ((n >> 8) & 0x00ff00ff) | ((n << 8) & 0xff00ff00);

	n = ((n >> 16) & 0x0000ffff) | ((n << 16) & 0xffff0000);

	return n;
}

void record_desktop_duplication::draw_cursor()
{
	if (_cursor_info.visible == false) return;

	int cursor_width = 0, cursor_height = 0, left = 0, top = 0;

	cursor_width = _cursor_info.shape.Width;
	cursor_height = _cursor_info.shape.Height;

	// In case that,the value of position is negative value
	left = abs(_cursor_info.position.x - _rect.left());
	top = abs(_cursor_info.position.y - _rect.top());

	// Notice here
	if (_cursor_info.shape.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME)
		cursor_height = cursor_height / 2;

	//Skip invisible pixel
	cursor_width = std::min((int)_imgBuf->w - left, cursor_width);
	cursor_height = std::min((int)_imgBuf->h - top, cursor_height);

	//al_debug("left:%d top:%d width:%d height:%d type:%d", left, top, cursor_width, height, _cursor_info.shape.Type);

	switch (_cursor_info.shape.Type)
	{

		// The pointer type is a color mouse pointer, 
		// which is a color bitmap. The bitmap's size 
		// is specified by width and height in a 32 bpp 
		// ARGB DIB format.
		// should trans cursor to BGRA?
	case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
	{
		unsigned int* cursor_32 = reinterpret_cast<unsigned int*>(_cursor_info.buff);
		unsigned int* screen_32 = reinterpret_cast<unsigned int*>(_imgBuf->img);

		for (int row = 0; row < cursor_height; row++) {
			for (int col = 0; col < cursor_width; col++) {
				unsigned int cur_cursor_val = cursor_32[col + (row * (_cursor_info.shape.Pitch / sizeof(UINT)))];

				//Skip black or empty value
				if (cur_cursor_val == 0x00000000)
					continue;
				else
					screen_32[(abs(top) + row) * _imgBuf->w + abs(left) + col] = cur_cursor_val;//bit_reverse(cur_cursor_val);
			}
		}
		break;
	}

	// The pointer type is a monochrome mouse pointer, 
	// which is a monochrome bitmap. The bitmap's size 
	// is specified by width and height in a 1 bits per 
	// pixel (bpp) device independent bitmap (DIB) format 
	// AND mask that is followed by another 1 bpp DIB format 
	// XOR mask of the same size.
	case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
	{
		unsigned int* cursor_32 = reinterpret_cast<unsigned int*>(_cursor_info.buff);
		unsigned int* screen_32 = reinterpret_cast<unsigned int*>(_imgBuf->img);

		for (int row = 0; row < cursor_height; row++) {
			BYTE MASK = 0x80;
			for (int col = 0; col < cursor_width; col++) {
				// Get masks using appropriate offsets
				BYTE AndMask = _cursor_info.buff[(col / 8) + (row * (_cursor_info.shape.Pitch))] & MASK;
				BYTE XorMask = _cursor_info.buff[(col / 8) + ((row + cursor_height) * (_cursor_info.shape.Pitch))] & MASK;
				UINT AndMask32 = (AndMask) ? 0xFFFFFFFF : 0xFF000000;
				UINT XorMask32 = (XorMask) ? 0x00FFFFFF : 0x00000000;

				// Set new pixel
				screen_32[(abs(top) + row) * _imgBuf->w + abs(left) + col] = (screen_32[(abs(top) + row) * _imgBuf->w + abs(left) + col] & AndMask32) ^ XorMask32;

				// Adjust mask
				if (MASK == 0x01)
				{
					MASK = 0x80;
				}
				else
				{
					MASK = MASK >> 1;
				}
			}
		}
		break;
	}
	// The pointer type is a masked color mouse pointer. 
	// A masked color mouse pointer is a 32 bpp ARGB format 
	// bitmap with the mask value in the alpha bits. The only 
	// allowed mask values are 0 and 0xFF. When the mask value
	// is 0, the RGB value should replace the screen pixel. 
	// When the mask value is 0xFF, an XOR operation is performed 
	// on the RGB value and the screen pixel; the result replaces the screen pixel.
	case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
	{
		unsigned int* cursor_32 = reinterpret_cast<unsigned int*>(_cursor_info.buff);
		unsigned int* screen_32 = reinterpret_cast<unsigned int*>(_imgBuf->img);

		for (int row = 0; row < cursor_height; row++) {
			for (int col = 0; col < cursor_width; col++) {
				unsigned int cur_cursor_val = cursor_32[col + (row * (_cursor_info.shape.Pitch / sizeof(UINT)))];
				unsigned int cur_screen_val = screen_32[(abs(top) + row) * _imgBuf->w + abs(left) + col];
				unsigned int mask_val = 0xFF000000 & cur_cursor_val;

				if (mask_val) {
					//0xFF: XOR operation is performed on the RGB value and the screen pixel
					cur_screen_val = (cur_screen_val ^ cur_cursor_val) | 0xFF000000;
				}
				else {
					//0x00: the RGB value should replace the screen pixel
					cur_screen_val = cur_cursor_val | 0xFF000000;
				}
			}
		}
		break;
	}
	default:
		break;
	}
}



void record_desktop_duplication::record_func()
{
	
	int64_t pre_pts = 0, dur = AV_TIME_BASE / _fps;

	int error = AE_NO;

#if 0
	if (attatch_desktop() != true) {
		al_fatal("duplication attach desktop failed :%s",
			system_error::error2str(GetLastError()).c_str());
		if (_on_error) _on_error(AE_DUP_ATTATCH_FAILED);
		return;
	}
#endif

	//Should init after desktop attatched
	//error = init_duplication();
	//if (error != AE_NO) {
	//	al_fatal("duplication initialize failed %s,last error :%s", err2str(error), 
	// system_error::error2str(GetLastError()).c_str());
	//	if (_on_error) _on_error(error);
	//	return;
	//}

	DXGI_OUTDUPL_FRAME_INFO frame_info;
	while (_running)
	{
		//Timeout is no new picture,no need to update
		if ((error = get_desktop_image(&frame_info)) == AE_TIMEOUT) continue;

		if (error != AE_NO) {
			while (_running)
			{
				Sleep(300);
				clean_duplication();
				if ((error = init_duplication()) != AE_NO) {
					//if (_on_error) _on_error(error);
					//todo hdd
				}
				else break;
			}

			continue;
		}

		if ((error = get_desktop_cursor(&frame_info)) == AE_NO)
			draw_cursor();

		free_duplicated_frame();


#if 0
		//save bmp to test

		BITMAPINFOHEADER   bi;

		bi.biSize = sizeof(BITMAPINFOHEADER);
		bi.biWidth = _width;
		bi.biHeight = _height * (-1);
		bi.biPlanes = 1;
		bi.biBitCount = 32;//should get from system color bits
		bi.biCompression = BI_RGB;
		bi.biSizeImage = 0;
		bi.biXPelsPerMeter = 0;
		bi.biYPelsPerMeter = 0;
		bi.biClrUsed = 0;
		bi.biClrImportant = 0;

		BITMAPFILEHEADER bf;
		bf.bfType = 0x4d42;
		bf.bfReserved1 = 0;
		bf.bfReserved2 = 0;
		bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		bf.bfSize = bf.bfOffBits + _width * _height * 4;

		FILE* fp = fopen("..\\save.bmp", "wb+");

		fwrite(&bf, 1, sizeof(bf), fp);
		fwrite(&bi, 1, sizeof(bi), fp);
		fwrite(_buffer, 1, _buffer_size, fp);

		fflush(fp);
		fclose(fp);
#endif

		_on_data(_imgBuf);
		time_t pts = av_time();
		
		do_sleep(dur, pre_pts, pts);

		pre_pts = pts;
	}

	//av_frame_free(&frame);
}

